import numpy as np
import random
from PIL import Image
import math

class Write_Actor():    # 写数据类
    limit = (0,255)
    rand_seed = 0
    pic_data = None
    pic_size = [0,0]
    idx = []

    def __init__(self,pic_data,limit,rand_seed):
        self.limit = limit  # 约束
        self.pic_data = np.array(pic_data)  # 图片数据
        self.rand_seed = rand_seed  # 随机数种子
        self.pic_size[0] = self.pic_data.shape[0]   # 图片尺寸
        self.pic_size[1] = self.pic_data.shape[1]

    def get_id(self,data_len):  # 获取编辑序列
        cnt = 0     # 统计生成了多少个序列
        idx = []    # 存储下标
        random.seed(self.rand_seed) # 使用随机数种子，确保前后一致
        self.idx.clear()    # 清空下表列表
        while cnt < data_len:
            tmp_idx = random.randint(0,self.pic_size[0]*self.pic_size[1] - 1)   # 生成随机数
            if tmp_idx not in idx:  # 检测是否在禁用下表内
                idx_i, idx_j = self.get_ij(tmp_idx)  # 转化为二维下表
                if self.check_ij(idx_i,idx_j):  # 检测像素块是否可选
                    idx.append(tmp_idx) # 添加入禁用列表
                    cnt = cnt + 1
                    idx_i, idx_j = self.get_ij(tmp_idx) # 导入列表
                    self.idx.append((idx_i, idx_j))
                    idx = idx + self.get_relate_num(idx_i,idx_j)   # 补充禁用下标

    def write_data(self,data_list):
        self.idx.clear()
        self.get_id(len(data_list))
        # print(self.idx)
        for i in range(len(data_list)):
            self.write_ij(self.idx[i][0],self.idx[i][1],data_list[i])

    def get_relate_num(self,idx_i,idx_j):   # 为防止重复处理某两个像素块，获取他的边缘块加入检测
        ans_list = []
        if idx_j == 0:  # 某行第一个
            ans_list.append(self.ij_num(idx_i,1))   # 加入右侧
        else:
            ans_list.append(self.ij_num(idx_i,idx_j-1)) # 否则前后加入
            ans_list.append(self.ij_num(idx_i,idx_j+1))
        return ans_list

    def ij_num(self,i,j):
        return self.pic_size[0]*i+j

    def get_ij(self,num):   # 转化下标
        i = num // self.pic_size[0]
        j = num % self.pic_size[1]
        return i,j

    def check_ij(self,i,j):     # 检测可否嵌入
        if i < 0 or i >= self.pic_size[0] - 1 or j < 0 or j >= self.pic_size[1]-1:  # 数组越界
            return False
        if self.pic_data[i][j] > self.pic_data[i][j + 1]:
            d = self.pic_data[i][j] - self.pic_data[i][j + 1]
        else:
            d = self.pic_data[i][j + 1] - self.pic_data[i][j]
        if d < self.limit[0] or d > self.limit[1]:  # 超出范围
            return False
        return True

    def write_ij(self,i,j,m):
        op = m  # 加入嵌入数值，计算新的差值
        od = int(op / 2)    # 二分
        base_data = self.limit[0] + od
        k = 0
        if self.pic_data[i][j] > self.pic_data[i][j+1]:
            k = self.pic_data[i][j] - self.pic_data[i][j+1]
        else:
            k = self.pic_data[i][j+1] - self.pic_data[i][j]
        min_d = base_data - od
        max_d = base_data + od
        if max_d > 200:
            delta = max_d - 200
            max_d = 200
            min_d = min_d - delta
        if min_d < 0:
            delta = 1 - min_d
            min_d = 1
            max_d = max_d + delta

        if self.pic_data[i][j] < self.pic_data[i][j+1]:
            self.pic_data[i][j] = min_d  # 均匀分入图片
            self.pic_data[i][j+1] = max_d
            if m % 2 == 1:
                self.pic_data[i][j + 1] = max_d + 1
        else:
            self.pic_data[i][j+1] = min_d  # 均匀分入图片
            self.pic_data[i][j] = max_d
            if m % 2 == 1:
                self.pic_data[i][j] = max_d +1
        h = 0
        if self.pic_data[i][j] > self.pic_data[i][j + 1]:
            h = self.pic_data[i][j] - self.pic_data[i][j + 1]
        else:
            h = self.pic_data[i][j + 1] - self.pic_data[i][j]
        if h < self.limit[0] or h < self.limit[0] >self.limit[1]:
            print('err',h,m,k)
            print(min_d,max_d,self.pic_data[i][j],self.pic_data[i][j+1])
            print()

    def get_pic(self):  # 返回结果图片
        return self.pic_data


class Read_Actor():    # 写数据类
    limit = (0,255)
    rand_seed = 0
    pic_data = None
    pic_size = [0,0]
    idx = []
    data_list = []

    def __init__(self,pic_data,limit,rand_seed):
        self.limit = limit  # 约束
        self.pic_data = np.array(pic_data)  # 图片数据
        self.rand_seed = rand_seed  # 随机数种子
        self.pic_size[0] = self.pic_data.shape[0]   # 图片尺寸
        self.pic_size[1] = self.pic_data.shape[1]

    def get_id(self,data_len):  # 获取编辑序列
        cnt = 0     # 统计生成了多少个序列
        idx = []    # 存储下标
        random.seed(self.rand_seed) # 使用随机数种子，确保前后一致
        self.idx.clear()    # 清空下表列表
        while cnt < data_len:
            tmp_idx = random.randint(0,self.pic_size[0]*self.pic_size[1] - 1)   # 生成随机数
            if tmp_idx not in idx:  # 检测是否在禁用下表内
                idx_i, idx_j = self.get_ij(tmp_idx)  # 转化为二维下表
                if self.check_ij(idx_i,idx_j):  # 检测像素块是否可选
                    idx.append(tmp_idx) # 添加入禁用列表
                    cnt = cnt + 1
                    idx_i, idx_j = self.get_ij(tmp_idx) # 导入列表
                    self.idx.append((idx_i, idx_j))
                    idx = idx + self.get_relate_num(idx_i,idx_j)   # 补充禁用下标

    def get_relate_num(self,idx_i,idx_j):   # 为防止重复处理某两个像素块，获取他的边缘块加入检测
        ans_list = []
        if idx_j == 0:  # 某行第一个
            ans_list.append(self.ij_num(idx_i,1))   # 加入右侧
        else:
            ans_list.append(self.ij_num(idx_i,idx_j-1)) # 否则前后加入
            ans_list.append(self.ij_num(idx_i,idx_j+1))
        return ans_list

    def ij_num(self,i,j):
        return self.pic_size[0]*i+j

    def get_ij(self,num):   # 转化下标
        i = num // self.pic_size[0]
        j = num % self.pic_size[1]
        return i,j

    def check_ij(self,i,j):     # 检测可否嵌入
        if i < 0 or i >= self.pic_size[0] - 1 or j < 0 or j >= self.pic_size[1]-1:  # 数组越界
            return False
        d = 0
        if self.pic_data[i][j] > self.pic_data[i][j+1]:
            d = self.pic_data[i][j] - self.pic_data[i][j+1]
        else:
            d = self.pic_data[i][j+1] - self.pic_data[i][j]
        if d < self.limit[0] or d > self.limit[1]:  # 超出范围
            return False
        return True

    def read_data(self,data_len):
        self.idx.clear()
        self.data_list.clear()
        self.get_id(data_len)
        for i in range(data_len):
            self.data_list.append(self.read_ij(self.idx[i][0],self.idx[i][1]))

    def read_ij(self,i,j):  # 获取数据
        m = abs(int(self.pic_data[i][j])-int(self.pic_data[i][j+1]))
        return m

    def get_data(self):  # 返回结果数据
        return self.data_list

def to_ascii(string):
    ascii_list = []
    for char in string:
        ascii_value = ord(char)
        ascii_list.append(ascii_value)
    return ascii_list

def from_ascii(ascii_list):
    string = ""
    for ascii_value in ascii_list:
        char = chr(ascii_value)
        string += char
    return string

def split_into_binary_string(numbers, n):
    binary_strings = [format(num, "0"+str(n)+"b") for num in numbers]  # 转换为 n 位二进制字符串
    binary_chunks = [binary_strings[i:i + n] for i in range(0, len(binary_strings), n)]  # 按照 n 个数字分割
    return ''.join(''.join(chunk) for chunk in binary_chunks)  # 合并所有分割后的二进制字符串


def combine_from_binary_string(binary_string, n):
    binary_chunks = [binary_string[i:i + n] for i in range(0, len(binary_string), n)]  # 按照 n 个数字分割
    check = len(binary_chunks[-1])
    if n - check > 0:
        for i in range(n-check):
            binary_chunks[-1] = binary_chunks[-1] + "0"
    numbers = [int(chunk, 2) for chunk in binary_chunks]  # 将二进制字符串转换为整数
    return numbers

def conver_data2_b(data,n):
    binary_string = split_into_binary_string(data, 8)
    reconstructed_numbers = combine_from_binary_string(binary_string, n)
    return reconstructed_numbers

def conver_data2_8b(bits,n):
    binary_string = split_into_binary_string(bits,n)
    if len(binary_string) % 8 != 0:
        n = len(binary_string) // 8
        d = ""
        for i in range(8*n):
            d = d + binary_string[i]
        binary_string = d
    reconstructed_numbers = combine_from_binary_string(binary_string, 8)
    return reconstructed_numbers

def actor(path,data_r,fw):
    data = Image.open(path)
    r, g, b = data.split()
    print(g)
    data = r
    data = np.array(data)
    a = Write_Actor(data, fw, 0)
    data_w = data_r
    data_w = to_ascii(data_w)
    l = fw[1] - fw[0] +1
    n = int(math.log2(l))
    data_w = conver_data2_b(data_w,n)
    for i in range(len(data_w)):
        data_w[i] = data_w[i] + fw[0]
    print(data_w)
    a.write_data(data_w)
    print('ok')
    r = a.get_pic()
    r = r.astype('uint8')
    r = Image.fromarray(r, 'L')
    print(r)
    image = Image.merge('RGB', (r, g, b))
    print('ok1')
    image.save('ans.png')
    return len(data_w)

def reader(path,len_data,fw):
    data = Image.open(path)
    r, g, b = data.split()
    data = r
    data = np.array(data)
    b = Read_Actor(data, fw, 0)
    l = fw[1] - fw[0] + 1
    n = int(math.log2(l))
    b.read_data(len_data)
    s = b.get_data()
    for i in range(len(s)):
        s[i] = s[i] - fw[0]
    s = conver_data2_8b(s,n)
    data_g = from_ascii(s)
    return data_g


if __name__ == "__main__":

    # [(161, 262), (176, 213), (17, 2), (108, 240),
    # (214, 245), (204, 43), (170, 35), (127, 131),
    # (200, 68), (150, 130)]
    #
    # [(161, 262), (176, 213), (17, 2), (108, 240),
    # (214, 245), (204, 43), (170, 35), (127, 131),
    # (200, 68), (150, 130)]

    data = Image.open('LN.jpg')
    data = data.convert('L')
    data = np.array(data)
    a = Write_Actor(data,(32,63),0)
    data_w = "hello word !"
    data_w = to_ascii(data_w)
    data_w = conver_data2_b(data_w, 5)
    d = conver_data2_8b(data_w, 5)
    print(d)
    data_g = from_ascii(d)
    print(data_g)
    for i in range(len(data_w)):
        data_w[i] = data_w[i] + 32
    print(data_w)
    data_len = len(data_w)
    a.write_data(data_w)
    print(a.idx)
    image_1 = a.get_pic()
    image = Image.fromarray(image_1, 'L')
    image.save('LN1.png')

    data = Image.open('LN1.png')
    data = np.array(data)
    b = Read_Actor(data, (32, 63), 0)
    b.read_data(data_len)
    print(b.idx)
    s = b.get_data()
    print(s)
    for i in range(len(s)):
        s[i] = s[i] - 32
    print(s)
    s = conver_data2_8b(s,5)
    data_g = from_ascii(s)
    print(data_g)

# 88 88
# 107 85
# 11 53
# 55 77
# 63 19
# 145 133
# 89 211
# 62 40
# 163 149
# 155 200

# 88 88
# 107 85
# 11 53
# 55 77
# 63 19
# 145 133
# 89 211
# 62 40
# 163 149
# 155 200